#include <iostream>
#include <sstream>
#include <cstdio>
#include <stdio.h>
#include <string>
#include <cstring>
#include <pthread.h>
#include <cstdlib>
#include <assert.h>
#include <map>
#include <vector>
#include <list>
#include <unistd.h>
extern "C"{
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/file.h>
#include "rvm.h"
}

using namespace std;
int debugOutputFlag;
static void debugOutput(string output){
  if (debugOutputFlag){
    fprintf(stderr, "%s\n", output.c_str());
  }
}
/*
 * rvm_init creates an rvm object, 
 * opens the log file
 */
rvm_t rvm_init(const char * directory){
  rvm_t newRVM = new rvm_info;
  string backingStore = string(directory);
  debugOutput("in init");
  string::reverse_iterator it;
  it = backingStore.rbegin();
  if (*it != '/'){
    backingStore.append("/");
  }
  debugOutput("created an rvm with backing store of " + backingStore);
  newRVM->backingStore = backingStore;

  struct stat st;
  if (stat(newRVM->backingStore.c_str(), &st) == -1){
    mkdir(newRVM->backingStore.c_str(), S_IRWXU | S_IRWXG);
  }
  newRVM->log = fopen((newRVM->backingStore + string(".log")).c_str(), "a+");
  if (newRVM->log == NULL){
    newRVM->log = fopen((newRVM->backingStore + string(".log")).c_str(), "w+");

  }
  if (newRVM->log == NULL){
        perror("further log open issue");
  }
  debugOutput("opened log");
  return newRVM;
}

/*
 * fileDoesExist() returns the size of the file, otherwise if it does not exist
 * returns -1
 */
static int fileDoesExist(string fileName){
  struct stat fileInfo;
  if ( (stat(fileName.c_str(), &fileInfo)) == -1){//file does not exist
    perror("fileDoesNotExist");
    return -1;
  }
  else{
    return fileInfo.st_size;
  }  
}


/*
 * play through the specified csegname inside the .log file
 */
static void updateBackingStore(rvm_t rvm, const char * csegname){
	
	
	string segmentname= string(csegname);
	int count,i;
	  char line[20000],*token,*record,*sname,*offset,*size,*data;
      fprintf(stderr,"Inside update backing store\n");
	  rewind(rvm->log);
	  while (fgets(line, 20000,rvm->log) != NULL)
	  {
		  token = strtok(line, "/"); //record
		  sname=strtok(NULL, "/"); //segname

		  fprintf(stderr, "record type/segname: %s %s\n",token,sname);
		 if((strcmp(token,"commit")==0) && (strcmp(sname,segmentname.c_str())==0)) //commit record
		  {
		
			  fprintf(stderr, "commit segname %s\n",sname);
			  offset=strtok(NULL, "/");  //offset
			  
			  fprintf(stderr, "commit offset %d\n",atoi(offset));
			  size=strtok(NULL, "/");  //size
			  
			  fprintf(stderr, "commit size %d\n",atoi(size));
			  data=strtok(NULL, "/"); //data
			  fprintf(stderr, "commit data %s\n",data);
			  
			  fprintf(stderr, "commit data in segs %s\n",data);
			  
			  map<string,mSeg *>::iterator rvmsegit = rvm->mSegs.find(segmentname);
			      		//fprintf(stderr, "Size:::::::::;; %d: %s\n\n", rvm->mSegs.size(), rvmsegit->first.c_str());
			      			
			          	if(rvmsegit!=rvm->mSegs.end())
			          	{
			      		rvmsegit->second->fd = open(("rvm_segments/"+segmentname).c_str(),O_RDWR, S_IRWXU | S_IRWXG); //open segfile to make changes
			      		
			      		 
			      		 //fseek((FILE *)rvmsegit->second->fd, logit->offset, SEEK_SET);
			      		 lseek(rvmsegit->second->fd, atoi(offset), SEEK_SET);
			      		 
			      		 //writing to file
			      		 if(write(rvmsegit->second->fd,data,atoi(size))<0)
			      		 {
			      		 	perror("write error");
			      		 	abort();
			      		       
			      		 }
			          	}
			  
			  count++;
			  
		  }

		 fprintf(stderr,"commit count: %d\n",count);
		 

		 fprintf(stderr,"next record\n");
		 
	  }

}

/*
 * rvm_map locks the directory and opens a backing file
 * if the file does not exist, it opens it up and writes its contents to it
 * if the file does exist and it is smaller than the requested size, it extends the size
 * if the file does exist and it is the same as requested size, it maps it
 * if the file does exist and it is larger than the requested size, rvm abort()
 * if the file has been previously mapped, abort()
 */
void * rvm_map(rvm_t rvm, const char * csegname, int size_to_create){
  string segname = rvm->backingStore + string(csegname);
  mSeg *currmSeg, *newmSeg = new mSeg;
//(mSeg*)malloc(sizeof(struct memorySegment));
  
  int dirFile = open((rvm->backingStore + string(".rvm")).c_str(), O_RDWR | O_CREAT , S_IRWXU | S_IRWXG);
  if (dirFile == -1){
    perror("dirFile");
  }
  //must lock the dirfile so no one else creates memory segments
  flock(dirFile, LOCK_EX);
  
  
  updateBackingStore(rvm,csegname);
  //rvm_truncate_log(rvm);

  //open backing file
  newmSeg->fd = open(segname.c_str(), O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG);
  if (newmSeg->fd >=0){
    debugOutput("got exclusive and opened a previously unexisting file");
    newmSeg->segName = string(csegname);
    newmSeg->size = size_to_create;
    for (int counter = 0; counter < size_to_create; counter++){
      if (write(newmSeg->fd, (void *)"asdfadfasd", 1) == -1){
	perror("write error");
	abort();
      }
    }
    newmSeg->virtualAddress = mmap(0, 
				   size_to_create, 
				   PROT_READ | PROT_WRITE, 
				   MAP_PRIVATE, 
				   newmSeg->fd, 
				   0);
    //copy contents of memory to file
    newmSeg->fd = lseek(newmSeg->fd, SEEK_SET, 0);
    debugOutput("created a new mSeg named " + segname);
  }
  else if (rvm->mSegs.find(string(csegname)) == rvm->mSegs.end()){
    debugOutput("file exists, but not added to rvm");
    //play through the log csegname
   
    if (fileDoesExist(segname) < size_to_create){ 
      debugOutput( "resizing segment " + string(segname));
      FILE * oldbackingFile = fopen(segname.c_str(), "rw");
      int oldsize;
      void * oldmap = mmap(0, 
			   fileDoesExist(segname), 
			   PROT_READ | PROT_WRITE, MAP_PRIVATE, 
			   fileno(oldbackingFile), 
			   0);
      newmSeg->segName = string(csegname);
      oldsize = fileDoesExist(segname);
      newmSeg->size = size_to_create;
      newmSeg->virtualAddress = mremap(oldmap, 
				       oldsize,
				       size_to_create, MREMAP_MAYMOVE);
      //munmap(oldmap, fileDoesExist(segname));
      
      //adds to the end of the file
      write(newmSeg->fd, (void *)((char*)newmSeg->virtualAddress + oldsize), 
	    newmSeg->size - oldsize);
      //	     (FILE *)lseek((int)newmSeg->fd, 0, SEEK_END));
    }
    else if (fileDoesExist(segname) == size_to_create){
      //same size, just map and go
      FILE * oldbackingFile = fopen(segname.c_str(), "r+");
      debugOutput("mapping up file of same size");
      newmSeg->segName = string(csegname);
      newmSeg->size = size_to_create;
      newmSeg->virtualAddress = mmap(0,
				     size_to_create,
				     PROT_READ  | PROT_WRITE,
				     MAP_PRIVATE,
				     fileno(oldbackingFile),
				     0);
      if(newmSeg->virtualAddress == MAP_FAILED){
	perror("map failed");
	abort();
      }
    }
    else{
      debugOutput( "tried to recreate with same segmentname" + string(segname));
      abort();
    }
  }
  flock(dirFile, LOCK_UN);
  debugOutput("close dirFile");
  close(dirFile);
  
  
  newmSeg->dirty = 0;
  rvm->mSegs[string(csegname)] = newmSeg;
  debugOutput("added " + segname + " to rvm");
  return newmSeg->virtualAddress;
}


/*
 * Remove segment from memory
 * closes the backing file
 */
void rvm_unmap(rvm_t rvm, void * segbase){
  map<string, mSeg*>::iterator it = rvm->mSegs.begin();
  while (it != rvm->mSegs.end()){
    if ((*it).second->virtualAddress == segbase){
      break;
    }
  }
  if ((*it).second->virtualAddress != segbase){
    debugOutput("segbase does not exist");
    abort();
  }
  if (it->second->dirty){
    debugOutput("you are unmapping a dirty segment! must abort or commit");
    abort();
  }
  if (munmap(it->second->virtualAddress, it->second->size) < 0){
    debugOutput( "could not unmap " + it->second->segName);
    abort();
  }
  debugOutput( "mSeg" + it->second->segName + " unmapped");
  close((int)it->second->fd);
  delete it->second;
  rvm->mSegs.erase(it);
  return;
}

/*
 * rvm_destroy  destroys the backing file
 */
void rvm_destroy(rvm_t rvm, const char * segname){
  debugOutput( "closing rvm with directory " + string(rvm->backingStore));
  if (unlink((rvm->backingStore + string(segname)).c_str()) == -1){
    debugOutput( "closing a nonexisting backing store");
  }
  return;
}

/*
 * begin_trans will lock the files associated with the segbases
 */
trans_t rvm_begin_trans(rvm_t rvm, int numsegs, void ** segbases){
  map<string,mSeg *>::iterator it = rvm->mSegs.begin();
  trans_t newTrans = new transactionInfo;
  newTrans->rvm = rvm;
  int mSegsLocked= 0, counter;
  debugOutput("beginning transaction with numsegs " + numsegs);
  while(it != rvm->mSegs.end()){
    for (counter = 0; counter < numsegs; counter++){//for each segbase
      if (*(segbases + counter) == it->second->virtualAddress){
	//if the same, then lock it
	if (flock((int)it->second->fd, LOCK_EX || LOCK_NB) == -1){
	  debugOutput("already locked " + it->second->segName);
	  return (trans_t) -1;
	}
	debugOutput("locking a mSeg");
	it->second->dirty = 0;
	newTrans->mSegs[*(segbases + counter)] = it->second;
	mSegsLocked++;
      }
    }
    it++;
  }
  if (mSegsLocked != numsegs){
    debugOutput("could not lock all required memory segments");
    return (trans_t)-1;
  }
  debugOutput("returning newTrans");
  return newTrans;
}
  

/*
 * writes the before state to file synchronously
 */
void rvm_about_to_modify(trans_t tid, void * segbase, int offset, int size){
  mSeg * currmSeg;
  transactionItem *newtransItem = new transactionItem;
  if (tid->mSegs.find(segbase) == tid->mSegs.end()){
    debugOutput("specified segbase does not exist");
    abort();
  }
  //specify that the segment is in a transaction
  currmSeg = tid->mSegs[segbase];
  newtransItem->transmSeg = currmSeg;
  newtransItem->size = size;
  newtransItem->offset = offset;
  currmSeg->dirty++;
  flock(fileno(tid->rvm->log), LOCK_EX);
  if (fprintf(tid->rvm->log, "init/%s/%d/%d/", currmSeg->segName.c_str(),offset, size) < 0){
    perror("fprintf error");
    abort();
  }
  fprintf(stderr,"here");
  fwrite((char *)segbase + offset , 1, size, tid->rvm->log);
  fprintf(tid->rvm->log, "\n");
  //write to disk
  fdatasync(fileno(tid->rvm->log));
  flock(fileno(tid->rvm->log), LOCK_UN);
  tid->transactions.push_back(newtransItem);
  return;
}

/*
 * writes the committed data block to disk
 */
void rvm_commit_trans(trans_t tid){
  vector<transactionItem *>::iterator it = tid->transactions.begin();
  flock(fileno(tid->rvm->log), LOCK_EX);
  if (flock < 0){
    perror("flock error");
  }
  while (it != tid->transactions.end()){
    if ((*it)->transmSeg->dirty){
      if (fprintf(tid->rvm->log, "commit/%s/%d/%d/", (*it)->transmSeg->segName.c_str(), (*it)->offset, (*it)->size ) < 0){
	debugOutput("fprintf ERROR");
	abort();
      }
      fwrite((void*)((char*)(*it)->transmSeg->virtualAddress + (*it)->offset), 
	     1, 
	     (*it)->size,
	     tid->rvm->log);
      fprintf(tid->rvm->log, "\n");
      (*it)->transmSeg->dirty--;
    }

    delete *it;
    it = tid->transactions.erase(it);
    
  }
  //write to disk
  fdatasync(fileno(tid->rvm->log));
  fwrite("!", 1, 1, tid->rvm->log);
  flock(fileno(tid->rvm->log), LOCK_UN);
  //flock((*it)->transmSeg->fd, LOCK_UN);
  read_log(tid->rvm);
  
  return;
}


/*
 * writes an abort to disk
 */
void rvm_abort_trans(trans_t tid){
  map <void *, mSeg*>::iterator mSegiterator = tid->mSegs.begin();
  while (mSegiterator != tid->mSegs.end()){
    munmap(mSegiterator->second->virtualAddress, 
	   mSegiterator->second->size);
    mSegiterator++;
  }
  mSegiterator = tid->mSegs.begin();
  //FILE * oldbackingFile;
  lseek(mSegiterator->second->fd, 0, SEEK_SET);
  while (mSegiterator != tid->mSegs.end()){
      debugOutput("remapping up file ");
      mSegiterator->second->virtualAddress = mmap(mSegiterator->second->virtualAddress,
				     mSegiterator->second->size,
				     PROT_READ  | PROT_WRITE,
				     MAP_PRIVATE,
				     mSegiterator->second->fd,
				     0);
      mSegiterator++;
  }


  vector<transactionItem *>::iterator it = tid->transactions.begin();
  while (it != tid->transactions.end()){
    if ((*it)->transmSeg->dirty){
      flock(fileno(tid->rvm->log), LOCK_EX);
      fprintf(tid->rvm->log,"abort/%s/%d/%d/", (*it)->transmSeg->segName.c_str(), (*it)->offset, (*it)->size);
      fprintf(tid->rvm->log, "\n");
      flock(fileno(tid->rvm->log), LOCK_UN);
      (*it)->transmSeg->dirty--;
    }
    else{
    }
    delete *it;
    tid->transactions.erase(it);
    flock((*it)->transmSeg->fd, LOCK_UN);
  }
  
}


/*
 * go through logfile, applying changes to the memory segment backing store file
 * new .log should be empty
 */
void rvm_truncate_log(rvm_t rvm){

  //creating a list of segments to store info about modified segments in log
  printf("inside trunc log\n");
  //map<string,mSeg *>::iterator rvmit = rvm->mSegs.begin(); //iterator through list of segs in rvm
  list<mlogSeg> segList; //list of segs found in log
  //list<mlogSeg>::iterator logit;
  //mlogSeg segs;
  int count,i;
  char line[20000],*token,*record,*sname,*offset,*size,*data;

  rewind(rvm->log);
  while (fgets(line, 20000,rvm->log) != NULL)
  {
	  token = strtok(line, "/"); //record

	  fprintf(stderr, "record type: %s\n",token);
	 if(strcmp(token,"commit")==0) //init record
	  {
		 mlogSeg segs;
		  segs.initflag = 1;
		  segs.commitflag = 1;
		  sname=strtok(NULL, "/"); //segname
		  segs.segName=sname;
		  fprintf(stderr, "commit segname %s\n",segs.segName.c_str());
		  offset=strtok(NULL, "/");  //offset
		  segs.offset=atoi(offset);
		  fprintf(stderr, "commit offset %d\n",segs.offset);
		  size=strtok(NULL, "/");  //size
		  segs.size=atoi(size);
		  fprintf(stderr, "commit size %d\n",segs.size);
		  data=strtok(NULL, "/"); //data
		  fprintf(stderr, "commit data %s\n",data);
		  segs.modifieddata=data; //added
		  fprintf(stderr, "commit data in segs %s\n",segs.modifieddata);
		  segList.push_back(segs);
		  
		  map<string,mSeg *>::iterator rvmsegit = rvm->mSegs.find(segs.segName.c_str());
		      		//fprintf(stderr, "Size:::::::::;; %d: %s\n\n", rvm->mSegs.size(), rvmsegit->first.c_str());
		      			
		          	if(rvmsegit!=rvm->mSegs.end())
		          	{
		      		rvmsegit->second->fd = open(("rvm_segments/"+segs.segName).c_str(),O_RDWR, S_IRWXU | S_IRWXG); //open segfile to make changes
		      		
		      		 
		      		 //fseek((FILE *)rvmsegit->second->fd, logit->offset, SEEK_SET);
		      		 lseek(rvmsegit->second->fd, segs.offset, SEEK_SET);
		      		 
		      		 //writing to file
		      		 if(write(rvmsegit->second->fd, segs.modifieddata, segs.size)<0)
		      		 {
		      		 	perror("write error");
		      		 	abort();
		      		       
		      		 }
		          	}
		  
		  count++;
		  
	  }

	 fprintf(stderr,"commit count: %d\n",count);
	 
	/* if(strcmp(token,"commit")==0) //commit record
	 {
		 logit = segList.begin(); 
		 token=strtok(NULL, "/"); //segname
		 fprintf(stderr,"commit segname : %s\n",token);
		 offset=strtok(NULL, "/");
		 fprintf(stderr,"commit offset : %d\n",atoi(offset));
		 size=strtok(NULL, "/");
		 fprintf(stderr,"commit size : %d\n",atoi(size));
		 data=strtok(NULL, "/");
		 fprintf(stderr,"data after check commit: %s\n",data);
		 
		 //list<mlogSeg>::iterator 
		 
		 
		 while(logit != segList.end())
		 {
			fprintf(stderr," logit segname before compare: %s\n",logit->segName.c_str());
			fprintf(stderr," logit offset before compare: %d\n",logit->offset);
			fprintf(stderr," logit size before compare: %d\n",logit->size);
			 if((strcmp(logit->segName.c_str(),token)==0) && (logit->offset == atoi(offset)) && (logit->size == atoi(size))) //search for seg
			 {
				 assert(logit->initflag==1);
				 logit->commitflag = 1;
				 //token=strtok(NULL, "/"); //offset
				 //logit->offset = atoi(token);
				 //token=strtok(NULL, "/"); //size
				 //logit->size = atoi(token);	 
				 
				 //insert log data into *modifieddata
				 //data=strtok(NULL, "/");
	
				 //fprintf(stderr,"data modified: %s\n",logit->modifieddata);
				 i++;
				 logit->modifieddata=data;
				 fprintf(stderr,"commit data modified inside compare: %s\n",logit->modifieddata);
				 goto check;
			 }
			 fprintf(stderr,"commit data modified outside compare: %s\n",logit->modifieddata);
			 				 
			 
			 logit++;
				 
		 }
		 //segList.erase(logit);
		 
	 }
	 check:*/
	 fprintf(stderr,"next record\n");
	 
	 //if abort record don't have to do anything
	 	  
	 
  }
     
 
  
  
  
	  //fprintf(stderr,"before writing changes i: %d",i);
  
	

    //after reading through entire log make changes now
    //list<mlogSeg>::iterator 
  	 	/* logit = segList.begin();
    while(logit != segList.end())
    {
    	string segname = logit->segName;
    	fprintf(stderr,"logit segname: %s\n",logit->segName.c_str());

    	fprintf(stderr,"logit data before: %s\n",logit->modifieddata);
    	fprintf(stderr,"logit commitflag : %d\n",logit->commitflag);
    	fprintf(stderr,"logit offset : %d\n",logit->offset);
    	if(logit->commitflag==1) //apply changes to rvm segs if commit is set
    	{
    		//string segname = logit->segName;
    		fprintf(stderr,"logit segname %s\n",segname.c_str());
        	map<string,mSeg *>::iterator rvmsegit = rvm->mSegs.find(segname);
    		//fprintf(stderr, "Size:::::::::;; %d: %s\n\n", rvm->mSegs.size(), rvmsegit->first.c_str());
    			
        	if(rvmsegit!=rvm->mSegs.end())
        	{
    		rvmsegit->second->fd = open(("rvm_segments/"+segname).c_str(),O_RDWR, S_IRWXU | S_IRWXG); //open segfile to make changes
    		
    		 
    		 //fseek((FILE *)rvmsegit->second->fd, logit->offset, SEEK_SET);
    		 lseek(rvmsegit->second->fd, logit->offset, SEEK_SET);
    		 
    		 //writing to file
    		 if(write(rvmsegit->second->fd, logit->modifieddata, logit->size)<0)
    		 {
    		 	perror("write error");
    		 	abort();
    		       
    		 }
        	}
    		 //rvm->mSegs.erase(rvmsegit); 
    		 //close(rvmsegit->second->fd);
    		 
    	}
    	logit++;

    } 
*/    //segList.erase(logit);
 
    //clear log file
    fclose(rvm->log);
    rvm->log = fopen((rvm->backingStore + string(".log")).c_str(), "w+");
       
}

void read_log(rvm_t rvm){

  //creating a list of segments to store info about modified segments in log
  printf("inside read log\n");
  //map<string,mSeg *>::iterator rvmit = rvm->mSegs.begin(); //iterator through list of segs in rvm
  list<mlogSeg> segList; //list of segs found in log
  //list<mlogSeg>::iterator logit;
  //mlogSeg segs;
  int count,i;
  char line[20000],*token,*record,*sname,*offset,*size,*data;

  rewind(rvm->log);
  while (fgets(line, 20000,rvm->log) != NULL)
  {
	  token = strtok(line, "/"); //record

	  fprintf(stderr, "record type: %s\n",token);
	 if(strcmp(token,"commit")==0) //init record
	  {
		 mlogSeg segs;
		  segs.initflag = 1;
		  segs.commitflag = 1;
		  sname=strtok(NULL, "/"); //segname
		  segs.segName=sname;
		  fprintf(stderr, "commit segname %s\n",segs.segName.c_str());
		  offset=strtok(NULL, "/");  //offset
		  segs.offset=atoi(offset);
		  fprintf(stderr, "commit offset %d\n",segs.offset);
		  size=strtok(NULL, "/");  //size
		  segs.size=atoi(size);
		  fprintf(stderr, "commit size %d\n",segs.size);
		  data=strtok(NULL, "/"); //data
		  fprintf(stderr, "commit data %s\n",data);
		  segs.modifieddata=data; //added
		  fprintf(stderr, "commit data in segs %s\n",segs.modifieddata);
		  segList.push_back(segs);
		  
		  map<string,mSeg *>::iterator rvmsegit = rvm->mSegs.find(segs.segName.c_str());
		      		//fprintf(stderr, "Size:::::::::;; %d: %s\n\n", rvm->mSegs.size(), rvmsegit->first.c_str());
		      			
		          	if(rvmsegit!=rvm->mSegs.end())
		          	{
		      		rvmsegit->second->fd = open(("rvm_segments/"+segs.segName).c_str(),O_RDWR, S_IRWXU | S_IRWXG); //open segfile to make changes
		      		
		      		 
		      		 //fseek((FILE *)rvmsegit->second->fd, logit->offset, SEEK_SET);
		      		 lseek(rvmsegit->second->fd, segs.offset, SEEK_SET);
		      		 
		      		 //writing to file
		      		 if(write(rvmsegit->second->fd, segs.modifieddata, segs.size)<0)
		      		 {
		      		 	perror("write error");
		      		 	abort();
		      		       
		      		 }
		          	}
		  
		  count++;
		  
	  }

	 fprintf(stderr,"commit count: %d\n",count);
	 

	 fprintf(stderr,"next record\n");
	 
  }
	 

       
}



/*
 * toggle the debugOutput
 */
void rvm_verbose(int enable_flag){
  debugOutputFlag = enable_flag;
  return;
}

